/*
 * BasicStroke.java  0.4.0 / Oct 24, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing.graphics;

import java.util.Arrays;

// TODO: write better comments

/**
 * Basic Stroke.
 *
 * <p>
 *   <tt>NOTE: This class is not compatible with <em>java.awt.BasicStroke</em>
 *             class!</tt>
 * </p>
 * <p>
 *   Dash phase is not supported. The odd elements maps opaque pen.
 * </p>
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public final class BasicStroke extends Stroke {

// Public Static Fields. -------------------------------------------------------

  /**
   * Miter line join style.
   */
  public static final int JOIN_MITER = 0;

  /**
   * Round line join style.
   */
  public static final int JOIN_ROUND = 1;

  /**
   * Bevel line join style.
   */
  public static final int JOIN_BEVEL = 2;

  /**
   * Flat line cap style.
   */
  public static final int CAP_BUTT = 0;

  /**
   * Round line cap style.
   */
  public static final int CAP_ROUND = 1;

  /**
   * Square line cap style.
   */
  public static final int CAP_SQUARE = 2;

// Private Fields. -------------------------------------------------------------

  private final float width;
  private final int cap;
  private final int join;
  private final float miterLimit;
  private final float[] dash;
  private final float dashPhase;
  private int hashCode; // NOPMD

// Public Constructors. --------------------------------------------------------

  /**
   * Constructs new BasicStroke stroke.
   *
   * @param width the stroke width
   * @param cap the line cap style
   * @param join the line join style
   * @param miterLimit the miter limit
   * @param dash the dash array
   * @param dashPhase the dash phase
   */
  public BasicStroke(float width, int cap, int join, float miterLimit,
                     float[] dash, float dashPhase) { // NOPMD
    if (width < 0.0f) {
      throw new IllegalArgumentException("Negative stroke width");
    }
    if ((cap < CAP_BUTT) || (cap > CAP_SQUARE)) {
      throw new IllegalArgumentException("Cap out of range");
    }
    if ((join < JOIN_MITER) || (join > JOIN_BEVEL)) {
      throw new IllegalArgumentException("Join out of range");
    } else if ((join == JOIN_MITER) && (miterLimit < 1.0f)) {
      throw new IllegalArgumentException("Miter limit < 1.0f");
    }
    if (dash != null) {
      int dashLength = dash.length;
      if (dashLength == 0) {
        throw new IllegalArgumentException("Empty dash array");
      }
      if (dashPhase < 0.0f) {
        throw new IllegalArgumentException("Negative dash phase");
      }
      boolean hasNonZeroDash = false;
      for (int i = dashLength - 1; i >= 0; i--) {
        float di = dash[i];
        if (di > 0.0) {
          hasNonZeroDash = true;
        } else if (di < 0.0) {
          throw new IllegalArgumentException("Negative dash length");
        }
      }
      if (!hasNonZeroDash) {
        throw new IllegalArgumentException("All dashes are 0.0f");
      }
    }
    this.width = width;
    this.cap = cap;
    this.join = join;
    this.miterLimit = miterLimit;
    if (dash == null) {
      this.dash = null;
    } else {
      this.dash = (float[])dash.clone();
    }
    this.dashPhase = dashPhase;
  }

  /**
   * Constructs new BasicStroke stroke.
   *
   * @param width the stroke width
   * @param cap the line cap style
   * @param join the line join style
   * @param miterLimit the miter limit
   * @param dash the dash array
   */
  public BasicStroke(float width, int cap, int join, float miterLimit,
                     float[] dash) {
    this(width, cap, join, miterLimit, dash, 0);
  }

  /**
   * Constructs new BasicStroke stroke.
   *
   * @param width the stroke width
   * @param cap the line cap style
   * @param join the line join style
   * @param miterLimit the miter limit
   */
  public BasicStroke(float width, int cap, int join, float miterLimit) {
    this(width, cap, join, miterLimit, null, 0);
  }

  /**
   * Constructs new BasicStroke stroke.
   *
   * @param width the stroke width
   * @param cap the line cap style
   * @param join the line join style
   */
  public BasicStroke(float width, int cap, int join) {
    this(width, cap, join, 10, null, 0);
  }

  /**
   * Constructs new BasicStroke stroke.
   *
   * @param width the stroke width
   */
  public BasicStroke(float width) {
    this(width, CAP_SQUARE, JOIN_MITER, 10, null, 0);
  }

  /**
   * Constructs new BasicStroke stroke.
   */
  public BasicStroke() {
    this(1, CAP_SQUARE, JOIN_MITER, 10, null, 0);
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Returns line width.
   *
   * @return line width
   */
  public float getLineWidth() {
    return width;
  }

  /**
   * Returns the end cap style.
   *
   * @return the end cap style
   */
  public int getEndCap() {
    return cap;
  }

  /**
   * Returns the line join style.
   *
   * @return the line join style
   */
  public int getLineJoin() {
    return join;
  }

  /**
   * Returns the miter limit.
   *
   * @return the miter limit
   */
  public float getMiterLimit() {
    return miterLimit;
  }

  /**
   * Returns the dash array.
   *
   * @return the dash array.
   */
  public float[] getDashArray() {
    if (dash != null) {
      return (float[])dash.clone();
    }
    return null; // NOFB
  }

  /**
   * Returns the dash phase.
   *
   * @return the dash phase
   */
  public float getDashPhase() {
    return dashPhase;
  }

// Overridden Methods. ---------------------------------------------------------

  /**
   * {@inheritDoc}
   */
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if ((obj != null) && (obj.getClass() == getClass())) {
      BasicStroke bs = (BasicStroke)obj;
      return ((Float.floatToIntBits(bs.getLineWidth()) ==
              Float.floatToIntBits(getLineWidth())) &&
              (bs.getEndCap() == getEndCap()) &&
              (bs.getLineJoin() == getLineJoin()) &&
              (Float.floatToIntBits(bs.getMiterLimit()) ==
              Float.floatToIntBits(getMiterLimit())) &&
              Arrays.equals(bs.getDashArray(), getDashArray()) &&
              (Float.floatToIntBits(bs.getDashPhase()) ==
              Float.floatToIntBits(getDashPhase())));
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  public int hashCode() {
    if (hashCode == 0) {
      int hashCode = 367;
      hashCode = 179 * hashCode + Float.floatToIntBits(getLineWidth());
      hashCode = 179 * hashCode + getEndCap();
      hashCode = 179 * hashCode + getLineJoin();
      hashCode = 179 * hashCode + Float.floatToIntBits(getMiterLimit());
      float[] da = getDashArray();
      if (da != null) {
        for (int i = da.length - 1; i >= 0; i--) {
          hashCode = 179 * hashCode + Float.floatToIntBits(da[i]);
        }
      }
      hashCode = 179 * hashCode + Float.floatToIntBits(getDashPhase());
      this.hashCode = hashCode;
    }
    return hashCode;
  }

}
